package com.tosmart.jorm.impl;

import com.tosmart.jorm.annotation.*;
import com.tosmart.jorm.face.OptimisticLockSupport;
import com.tosmart.jorm.util.StringUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * JORM the basic framework for reflection support.
 *
 * @author tosmart@gmail.com
 * @see com.tosmart.jorm.impl.JormImpl
 */
public class ReflectBase {
    /**
     * The use of reflection to get Entity corresponding
     * database table name.
     * <p/>
     * if the 'table' of @JormEntity not present, return
     * the Uppercase of the Entity name.
     *
     * @param entityClass Entity class
     * @return table name assigned with the Entity class
     */
    public static String getEntityTable(Class entityClass) {

        @SuppressWarnings({"unchecked"})
        JormEntity entity = (JormEntity)
                entityClass.getAnnotation(JormEntity.class);

        return entity.table().length() > 0
                ? entity.table().toUpperCase()
                : entityClass.getSimpleName().toUpperCase();
    }

    /**
     * The use of reflection to get Entity corresponding
     * the name of optimistic Lock's version column.
     *
     * @param entityClass Entity class
     * @return the name of optimistic Lock's version column
     */
    public static String optimisticLock(Class entityClass) {

        @SuppressWarnings({"unchecked"})
        JormEntity jormEntity = (JormEntity)
                entityClass.getAnnotation(JormEntity.class);

        return jormEntity.optimisticLock();
    }

    /**
     * The use of reflection to get Entity Field corresponding
     * the name of database table column.
     *
     * @param field Entity field instance
     * @return the name of database table column
     */
    public static String getOrmMapColumn(Field field) {

        JormMap jormMap = field.getAnnotation(JormMap.class);
        if (jormMap != null && jormMap.column().length() > 0) {
            return jormMap.column().toUpperCase();
        }

        return field.getName().toUpperCase();
    }

    /**
     * The use of reflection to get Entity Field corresponding
     * automatic generate method.
     *
     * @param field Entity field instance
     * @return automatic generate method
     */
    public static GenerateMethod getAutoGenerateMethod(Field field) {
        JormMap jormMap = field.getAnnotation(JormMap.class);
        if (jormMap != null) return jormMap.autoGenerate();
        return GenerateMethod.None;
    }

    /**
     * The use of reflection to get Entity Field corresponding
     * Oracle sequence's name.
     *
     * @param field Entity field instance
     * @return assigned oracle sequence's name
     */
    public static String getOracleSequence(Field field) {
        JormMap jormMap = field.getAnnotation(JormMap.class);
        return jormMap != null ? jormMap.oracleSequence() : "";
    }

    /**
     * List all the fields of Entity class, which marks
     * with the 'ormClass' Annotation
     *
     * @param entityClass Entity class
     * @param ormClass    JormMap/JormRefer/JormReferList
     * @return the fields list
     */
    public static List<Field> getOrmFields(
            Class entityClass, Class ormClass) {

        List<Field> list = new ArrayList<Field>();

        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            //noinspection unchecked
            if (field.isAnnotationPresent(ormClass))
                list.add(field);
        }

        Class superClass = entityClass.getSuperclass();
        if (superClass != null) {
            list.addAll(getMapFields(superClass));
        }

        return list;
    }

    /**
     * List all the fields of Entity class, which marks
     * with the JormMap Annotation
     *
     * @param entityClass Entity class
     * @return the fields list
     */
    @SuppressWarnings({"unchecked"})
    public static List<Field> getMapFields(Class entityClass) {
        return ReflectBase.getOrmFields(entityClass, JormMap.class);
    }

    /**
     * List all the fields of Entity class, which marks
     * with the JormRefer Annotation
     *
     * @param entityClass Entity class
     * @return the fields list
     */
    public static List<Field> getReferFields(Class entityClass) {
        return ReflectBase.getOrmFields(entityClass, JormRefer.class);
    }

    /**
     * List all the fields of Entity class, which marks
     * with the JormReferList Annotation
     *
     * @param entityClass Entity class
     * @return the fields list
     */
    public static List<Field> getReferListFields(Class entityClass) {
        return ReflectBase.getOrmFields(entityClass, JormReferList.class);
    }

    /**
     * The use of reflection to get Entity corresponding
     * the 'foreignKeys' value of @JormRefer
     *
     * @param field Entity class field marks with @JormRefer
     * @return the foreignKeys list
     */
    public static String[] getOrmReferForeignKeys(Field field) {
        return field.getAnnotation(JormRefer.class).foreignKeys();
    }

    /**
     * The use of reflection to get Entity corresponding
     * the 'entityClass' value of @JormReferList
     *
     * @param field Entity class field marks with @JormReferList
     * @return the Class of the Field instance
     */
    public static Class getOrmReferListEntityClass(Field field) {
        return field.getAnnotation(JormReferList.class).entityClass();
    }

    /**
     * The use of reflection to get Entity corresponding
     * the 'foreignKeys' value of @JormReferList
     *
     * @param field Entity class field marks with @JormReferList
     * @return the foreignKeys list
     */
    public static String[] getOrmReferListForeignKeys(Field field) {
        return field.getAnnotation(JormReferList.class).foreignKeys();
    }

    /**
     * Verify the legitimacy of Entity class.
     * If the validation fails, throw RuntimeException.
     *
     * @param aClass Entity class
     */
    public static void validEntityClass(Class aClass) {

        if (!aClass.isAnnotationPresent(JormEntity.class)) {
            throw new RuntimeException(
                    String.format(
                            "*%s* is not a @JormEntity Marked Class.",
                            aClass.getSimpleName()
                    )
            );
        }

        if (ReflectBase.getOrmFields(aClass, JormMap.class).isEmpty()) {
            throw new RuntimeException(
                    String.format(
                            "*%s* NOT has any @JormMap Field.",
                            aClass.getSimpleName()
                    )
            );
        }

        if (JormReflect.useOptimisticLock(aClass)) {

            boolean implementsInterface = false;
            Class[] interfaces = aClass.getInterfaces();

            for (Class anInterface : interfaces) {
                if (anInterface.equals(OptimisticLockSupport.class)) {
                    implementsInterface = true;
                    break;
                }
            }

            if (!implementsInterface) {
                throw new RuntimeException(
                        String.format("*%s* use optimisticLock, " +
                                "But not implements interface OptimisticLockSupport.",
                                aClass.getSimpleName()
                        )
                );
            }

            String versionColumn =
                    ReflectBase.optimisticLock(aClass);

            if (StringUtil.isEmpty(versionColumn)) {
                throw new RuntimeException(
                        String.format("*%s* use optimisticLock, " +
                                "But 'versionColumn' not setted.",
                                aClass.getSimpleName()
                        )
                );
            }
        }
    }
}
